深入ES6 (五) class

前言

今天来简单的聊一聊ES6中的class。我们都知道javascript这们语言能火起来,无非是ajax的兴起,而javascript中的面相对象,甚是让人难以理解,与传统语言的面向对象有很大差别,在ES6中,ECMAscript标准也看到了js中面向对象的不便,所以就有了今天的class。


正文从这里开始,ES6中的class从用法上可以理解是和传统语言的面向对象一样的思想,但是实际上class的面向对象思想还是javascript中的原型链思想,所以可以理解为ES6中的class就是一个语法糖,除了方便我们使用,大部分功能ES5都有。


class的用法

1
2
3
4
5
6
7
8
9
10
11
//定义类
class test {
constructor(x, y) {
this.x = x;
this.y = y;
}
tezmlName(){
console.log("黄兆楠")
}
}

上面的例子就定义了一个类,constructor代表着test该类创建出来时候就执行的内容。也就是我们所谓的init。

我们可以在类中写方法,如上。注意,在class中自定义方法不需要加function关键字,方法之间也不需要逗号分隔。


1
2
3
4
5
6
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true

上面这块代码验证了ES6中的class只是方便我们使用的一个语法糖,其class数据类型为函数,使用的时候也是直接new,与构造函数使用方式一样


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Point {
constructor() {
// ...
}
toString() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
};

class中的自定义方法实际就放在了函数的prototype中


类的实际对象

1
2
3
4
5
6
7
8
9
class Point {
// ...
}
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);

生成类的实例对象的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。

class表达式

1
2
3
4
5
6
const MyClass = new class {
getName() {
return "tezml";
}
};
console.log(MyClass.tezml) //tezml

像函数一样,class可以被定义,也可以给

class的继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

1
2
3
4
5
6
7
8
9
10
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}

上面这块代码中有一个新的用法:super,super的意思表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}

在子类的构造函数中,只有调用了super,才可以使用this,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

super

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

1、函数

1
2
3
4
5
6
7
class A {}
class B extends A {
constructor() {
super();
}
}

super作为函数调用时,代表父类的构造函数。(上文已提到)ES6 要求,子类的构造函数必须执行一次super函数。

2、对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();

super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。


ES6 规定,通过super调用父类的方法时,super会绑定子类的this。

Class 实现 mixin

我第一次接触mixin这个名词时候以为是中文汉语拼音’迷信’✧(≖ ◡ ≖✿),后来熟悉了之后才知道是’mix in’,混入的意思,实际上也就实现了模块化的需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"
&& key !== "prototype"
&& key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}

Decorator

很多面向对象的语言都有修饰器,比如C#。在2016年的ES7中,Decorator被引入到javascript中,其实在angular2中,已经有了Decorator的概念,这次ES7发布,ECMAscript把Decorator引入到标准中。

Decorator的意思是修饰器,用来修改类的行为,大概的用法如下

1
2
3
4
5
6
7
8
9
10
@readOnlyDecorator
class tezmlClass {
// ...
}
function readOnlyDecorator(target) {
target.readOnly = true;
}
tezmlClass.readOnly // true

上面这个例子的意思是,可以通过 @readOnlyDecorator这个修饰器修改tezmlClass这个类的属性或方法。target这个参数的意思是指调用该函数的类,既:tezmlClass

其使用场景是通过修饰器来处理只读属性。

方法的修饰

1
2
3
4
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}

Decorator不光可以修饰类,也可以修饰类中的方法

修饰符的用处一方面是修改或者添加方法和属性,还一个功能在于注释,清晰的体现出一个方法或者类的场景或者用法

函数不能调用修饰符

1
2
3
4
5
6
7
8
9
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}

上面这块代码执行后counter还会是0,原因在于函数会有函数提升的效果,所以会先执行 @add 和 foo(),而执行@add时,add这个变量只被命名,而并没有被赋值,所以修饰符不可被用于函数

Decorator处理mixins

前文提到了Class的mixins,而Decorator也可实现mixins。

具体实现方法这里先不码了,有时间做一期Mixins专期。